package cn.infrabase.core.io;


import cn.infrabase.core.io.file.FileNameUtils;
import cn.infrabase.core.thread.ThreadUtils;
import lombok.extern.slf4j.Slf4j;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;



/**
 * 文件及目录工具类。
 *
 * @author Weimin Gao
 * @since 1.0.0
 **/
@Slf4j
public class FileUtils {

    /**
     * 断言当前运行环境是否为Windows系统。
     *
     * @return 真（true）表示当前运行环境是Windows系统。
     */
    public static boolean isWindows() {
        return FileNameUtils.WINDOWS_SEPARATOR == File.separatorChar;
    }

    /**
     * 返回给定文件的输入流。
     *
     * @param file 给定的文件。
     * @return 输入流对象。
     * @throws IoRuntimeException 如果给定的文件未找到时。
     */
    public static BufferedInputStream getInputStream(File file) throws IoRuntimeException {
        return IoUtils.toBuffered(IoUtils.toStream(file));
    }

    /**
     * <pre>
     * 返回给定文件或目录指定层级的路径。
     * 说明：
     * 1：如果给定的文件或目录为null或指定的层级小于1时，则直接返回给定的文件或目录。
     * 2：如果指定的层级大于给定文件或目录的层级时，则返回null。
     * 3：如果给定文件或目录不存在时，此方法不会创建对应的文件或目录。
     * 例子：
     * 1、assertEquals("D:\\a\\b\\c\\d\\test.txt",FileUtils.getParent(new File("D:\\a\\b\\c\\d\\test.txt"),0).getAbsolutePath());
     * 2、assertEquals("D:\\a\\b\\c\\d",FileUtils.getParent(new File("D:\\a\\b\\c\\d\\test.txt"),1).getAbsolutePath());
     * 3、assertEquals("D:\\a\\b\\c",FileUtils.getParent(new File("D:\\a\\b\\c\\d\\test.txt"),2).getAbsolutePath());
     * 4、assertEquals("D:\\a\\b",FileUtils.getParent(new File("D:\\a\\b\\c\\d\\test.txt"),3).getAbsolutePath());
     * 5、assertEquals("D:\\a",FileUtils.getParent(new File("D:\\a\\b\\c\\d\\test.txt"),4).getAbsolutePath());
     * 6、assertEquals("D:\\",FileUtils.getParent(new File("D:\\a\\b\\c\\d\\test.txt"),5).getAbsolutePath());
     * 7、assertNull(FileUtils.getParent(new File("D:\\a\\b\\c\\d\\test.txt"),6));
     * 8、assertEquals("D:\\a\\b\\c\\d",FileUtils.getParent(new File("D:\\a\\b\\c\\d"),0).getAbsolutePath());
     * 9、assertEquals("D:\\a\\b\\c",FileUtils.getParent(new File("D:\\a\\b\\c\\d"),1).getAbsolutePath());
     * 10、assertEquals("D:\\a\\b",FileUtils.getParent(new File("D:\\a\\b\\c\\d"),2).getAbsolutePath());
     * 11、assertEquals("D:\\a",FileUtils.getParent(new File("D:\\a\\b\\c\\d"),3).getAbsolutePath());
     * 12、assertEquals("D:\\",FileUtils.getParent(new File("D:\\a\\b\\c\\d"),4).getAbsolutePath());
     * 13、assertNull(FileUtils.getParent(new File("D:\\a\\b\\c\\d"),5));
     * </pre>
     *
     * @param file  给定的文件或目录。
     * @param level 指定的层级。
     * @return 文件或目录。
     */
    public static File getParent(File file, int level) {
        if (level < 1 || null == file) {
            return file;
        }
        File parent;
        try {
            parent = file.getCanonicalFile().getParentFile();
        } catch (IOException e) {
            throw new IoRuntimeException(e,String.format("返回给定文件或目录指定层级的路径发生错误【%s】", e.getMessage()));
        }
        if (1 == level) {
            return parent;
        }
        return getParent(parent, level - 1);
    }

    /**
     * <pre>
     * 安全的级联创建给定待创建的目录。
     * 说明：
     * 1、此方法不对传入的File对象类型做判断，因为当File不存在时无法判断其类型。
     * 2、如果传入的File对象为null时，则返回null。
     * 例子：
     * 1、assertTrue(FileUtils.mkdir(new File("D:\\a\\d\\test.txt")).isDirectory());
     * </pre>
     *
     * @param dir      给定待创建的目录。
     * @param tryCount 最大尝试次数。
     * @param millis   线程等待的毫秒数。
     * @return 真（true）表示创建成功，否则表示创建失败。
     */
    @SuppressWarnings("all")
    protected static boolean mkDirs(File dir, int tryCount, long millis) {
        if (dir == null) {
            return false;
        }
        if (dir.isDirectory()) {
            return true;
        }
        // 高并发场景下，可以看到 i 处于 1 ~ 3 之间
        for (int i = 1; i <= tryCount; i++) {
            // 如果文件已存在，也会返回false，所以该值不能作为是否能创建的依据，因此不对其进行处理
            boolean success = dir.mkdirs();
            boolean exists = dir.exists();
            if (!success && exists) {
                log.trace("dir.mkdirs() return false , dir.exists() return true");
            }
            if (exists) {
                log.trace("创建目录【{}】成功", dir);
                return true;
            }
            ThreadUtils.sleep(millis);
        }
        return dir.exists();
    }

    /**
     * <pre>
     * 安全的级联创建给定待创建的目录，会递归自动创建其不存在的父文件夹，如果存在直接返回此文件夹。
     * 说明：
     * 1、此方法不对传入的File对象类型做判断，因为当File不存在时无法判断其类型。
     * 2、如果传入的File对象为null时，则返回null。
     * 例子：
     * 1、assertTrue(FileUtils.mkdir(new File("D:\\a\\d\\test.txt")).isDirectory());
     * </pre>
     *
     * @param dir 待创建的目录。
     * @return 创建的目录。
     */
    public static File mkDirs(File dir) {
        if (dir == null) {
            return null;
        }
        if (!dir.exists()) {
            boolean success = mkDirs(dir, 5, 1);
            if (!success && !dir.exists()) {
                log.debug("call mkdir(dir, 5, 1) return false && dir.exists() return false");
            }
        }
        return dir;
    }

    /**
     * <pre>
     * 创建给定文件或目录的父目录。
     * 说明：
     * 1、此方法不对传入的File对象类型做判断，因为当File不存在时无法判断其类型。
     * 2、如果传入的File对象为null时则返回null。
     * 例子：
     * 1、assertNull(FileUtils.mkParentDirs(null));
     * 2、assertEquals("D:\\aaa\\bbb", FileUtils.mkParentDirs(new File("D:\\aaa\\bbb\\test.txt")).getAbsolutePath());
     * 3、assertEquals("D:\\aaaa", FileUtils.mkParentDirs(new File("D:\\aaaa\\bbbb")).getAbsolutePath());
     * </pre>
     *
     * @param file 给定的文件或目录。
     * @return 父目录。
     */
    public static File mkParentDirs(File file) {
        if (null == file) {
            return null;
        }
        return mkDirs(getParent(file, 1));
    }

    /**
     * <pre>
     * 获取系统运行程序用户家目录路径（绝对路径）。
     * 例子：
     * 1、assertEquals(System.getProperty("user.home"), FileUtils.getUserHomePath());
     * </pre>
     *
     * @return 当前用户家目录路径，如：Windows系统C:\Users\gaoweimin。
     */
    public static String getUserHomePath() {
        return System.getProperty("user.home");
    }


}
